package org.jasig.cas;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.validation.constraints.NotNull;

import org.apache.commons.lang.StringUtils;
import org.jasig.cas.authentication.Authentication;
import org.jasig.cas.authentication.AuthenticationManager;
import org.jasig.cas.authentication.MutableAuthentication;
import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.principal.Credentials;
import org.jasig.cas.authentication.principal.PersistentIdGenerator;
import org.jasig.cas.authentication.principal.Principal;
import org.jasig.cas.authentication.principal.Service;
import org.jasig.cas.authentication.principal.ShibbolethCompatiblePersistentIdGenerator;
import org.jasig.cas.authentication.principal.SimplePrincipal;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
import org.jasig.cas.services.RegisteredService;
import org.jasig.cas.services.ServicesManager;
import org.jasig.cas.services.UnauthorizedProxyingException;
import org.jasig.cas.services.UnauthorizedServiceException;
import org.jasig.cas.services.UnauthorizedSsoServiceException;
import org.jasig.cas.ticket.ExpirationPolicy;
import org.jasig.cas.ticket.InvalidTicketException;
import org.jasig.cas.ticket.ServiceTicket;
import org.jasig.cas.ticket.Ticket;
import org.jasig.cas.ticket.TicketCreationException;
import org.jasig.cas.ticket.TicketException;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.jasig.cas.ticket.TicketGrantingTicketImpl;
import org.jasig.cas.ticket.TicketValidationException;
import org.jasig.cas.ticket.registry.TicketRegistry;
import org.jasig.cas.util.UniqueTicketIdGenerator;
import org.jasig.cas.validation.Assertion;
import org.jasig.cas.validation.ImmutableAssertionImpl;
import org.perf4j.aop.Profiled;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import com.callbell.cas.exception.TicketCaptchaException;
import com.github.inspektr.audit.annotation.Audit;

/**
 * Concrete implementation of a CentralAuthenticationService, and also the
 * central, organizing component of CAS's internal implementation.
 * <p>
 * This class is threadsafe.
 * <p>
 * This class has the following properties that must be set:
 * <ul>
 * <li> <code>ticketRegistry</code> - The Ticket Registry to maintain the list of
 * available tickets.</li>
 * <li> <code>serviceTicketRegistry</code> - Provides an alternative to configure
 * separate registries for TGTs and ST in order to store them in different
 * locations (i.e. long term memory or short-term)</li>
 * <li> <code>authenticationManager</code> - The service that will handle
 * authentication.</li>
 * <li> <code>ticketGrantingTicketUniqueTicketIdGenerator</code> - Plug in to
 * generate unique secure ids for TicketGrantingTickets.</li>
 * <li> <code>serviceTicketUniqueTicketIdGenerator</code> - Plug in to generate
 * unique secure ids for ServiceTickets.</li>
 * <li> <code>ticketGrantingTicketExpirationPolicy</code> - The expiration policy
 * for TicketGrantingTickets.</li>
 * <li> <code>serviceTicketExpirationPolicy</code> - The expiration policy for
 * ServiceTickets.</li>
 * </ul>
 *
 * @author William G. Thompson, Jr.
 * @author Scott Battaglia
 * @author Dmitry Kopylenko
 * @version $Revision: 1.16 $ $Date: 2007/04/24 18:11:36 $
 * @since 3.0
 */
public final class CentralAuthenticationServiceImpl implements CentralAuthenticationService {

	/** Log instance for logging events, info, warnings, errors, etc. */
	private final Logger log = LoggerFactory.getLogger(this.getClass());

	/** TicketRegistry for storing and retrieving tickets as needed. */
	@NotNull
	private TicketRegistry ticketRegistry;

	/**
	 * New Ticket Registry for storing and retrieving services tickets. Can
	 * point to the same one as the ticketRegistry variable.
	 */
	@NotNull
	private TicketRegistry serviceTicketRegistry;

	/**
	 * AuthenticationManager for authenticating credentials for purposes of
	 * obtaining tickets.
	 */
	@NotNull
	private AuthenticationManager authenticationManager;

	/**
	 * UniqueTicketIdGenerator to generate ids for TicketGrantingTickets
	 * created.
	 */
	@NotNull
	private UniqueTicketIdGenerator ticketGrantingTicketUniqueTicketIdGenerator;

	/** Map to contain the mappings of service->UniqueTicketIdGenerators */
	@NotNull
	private Map<String, UniqueTicketIdGenerator> uniqueTicketIdGeneratorsForService;

	/** Expiration policy for ticket granting tickets. */
	@NotNull
	private ExpirationPolicy ticketGrantingTicketExpirationPolicy;

	/** ExpirationPolicy for Service Tickets. */
	@NotNull
	private ExpirationPolicy serviceTicketExpirationPolicy;

	/** Implementation of Service Manager */
	@NotNull
	private ServicesManager servicesManager;

	/** Encoder to generate PseudoIds. */
	@NotNull
	private PersistentIdGenerator persistentIdGenerator = new ShibbolethCompatiblePersistentIdGenerator();

	private int maximumSessions = -1; // 同账号最大登录数，-1默认为不限制 gjwu@isoftstone.com

	/**
	 * Implementation of destoryTicketGrantingTicket expires the ticket provided
	 * and removes it from the TicketRegistry.
	 *
	 * @throws IllegalArgumentException
	 *             if the TicketGrantingTicket ID is null.
	 */
	@Audit(action = "TICKET_GRANTING_TICKET_DESTROYED", actionResolverName = "DESTROY_TICKET_GRANTING_TICKET_RESOLVER", resourceResolverName = "DESTROY_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER")
	@Profiled(tag = "DESTROY_TICKET_GRANTING_TICKET", logFailuresSeparately = false)
	@Transactional(readOnly = false)
	public void destroyTicketGrantingTicket(final String ticketGrantingTicketId) {
		Assert.notNull(ticketGrantingTicketId);

		if (log.isDebugEnabled()) {
			log.debug("Removing ticket [" + ticketGrantingTicketId + "] from registry.");
		}
		final TicketGrantingTicket ticket = (TicketGrantingTicket) this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);

		if (ticket == null) {
			return;
		}

		if (log.isDebugEnabled()) {
			log.debug("Ticket found.  Expiring and then deleting.");
		}
		ticket.expire();
		this.ticketRegistry.deleteTicket(ticketGrantingTicketId);
	}

	/**
	 * @throws IllegalArgumentException
	 *             if TicketGrantingTicket ID, Credentials or Service are null.
	 */
	@Audit(action = "SERVICE_TICKET", actionResolverName = "GRANT_SERVICE_TICKET_RESOLVER", resourceResolverName = "GRANT_SERVICE_TICKET_RESOURCE_RESOLVER")
	@Profiled(tag = "GRANT_SERVICE_TICKET", logFailuresSeparately = false)
	@Transactional(readOnly = false)
	public String grantServiceTicket(final String ticketGrantingTicketId, final Service service, final Credentials credentials) throws TicketException {
		Assert.notNull(ticketGrantingTicketId,"ticketGrantingticketId cannot be null");
		Assert.notNull(service, "service cannot be null");

		final TicketGrantingTicket ticketGrantingTicket;
		ticketGrantingTicket = (TicketGrantingTicket) this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);

		if (ticketGrantingTicket == null) {
			throw new InvalidTicketException();
		}

		synchronized (ticketGrantingTicket) {
			if (ticketGrantingTicket.isExpired()) {
				this.ticketRegistry.deleteTicket(ticketGrantingTicketId);
				throw new InvalidTicketException();
			}
		}

		final RegisteredService registeredService = this.servicesManager.findServiceBy(service);

		if (registeredService == null || !registeredService.isEnabled()) {
			log.warn("ServiceManagement: Unauthorized Service Access. Service ["+ service.getId() + "] not found in Service Registry.");
			throw new UnauthorizedServiceException();
		}

		if (!registeredService.isSsoEnabled() && credentials == null && ticketGrantingTicket.getCountOfUses() > 0) {
			log.warn("ServiceManagement: Service Not Allowed to use SSO.  Service ["+ service.getId() + "]");
			throw new UnauthorizedSsoServiceException();
		}

		// CAS-1019
		final List<Authentication> authns = ticketGrantingTicket.getChainedAuthentications();
		if (authns.size() > 1) {
			if (!registeredService.isAllowedToProxy()) {
				final String message = String.format("ServiceManagement: Service Attempted to Proxy, but is not allowed. Service: [%s] | Registered Service: [%s]", service.getId(), registeredService.toString());
				log.warn(message);
				throw new UnauthorizedProxyingException(message);
			}
		}

		if (credentials != null) {
			try {
				final Authentication authentication = this.authenticationManager.authenticate(credentials);
				final Authentication originalAuthentication = ticketGrantingTicket.getAuthentication();

				if (!(authentication.getPrincipal().equals(originalAuthentication.getPrincipal()) && authentication.getAttributes().equals(originalAuthentication.getAttributes()))) {
					throw new TicketCreationException();
				}
			} catch (final AuthenticationException e) {
				throw new TicketCreationException(e);
			}
		}

		final UniqueTicketIdGenerator serviceTicketUniqueTicketIdGenerator = this.uniqueTicketIdGeneratorsForService.get(service.getClass().getName());

		final ServiceTicket serviceTicket = ticketGrantingTicket.grantServiceTicket(serviceTicketUniqueTicketIdGenerator.getNewTicketId(ServiceTicket.PREFIX), service, this.serviceTicketExpirationPolicy, credentials != null);

		this.serviceTicketRegistry.addTicket(serviceTicket);

		if (log.isInfoEnabled()) {
			final List<Authentication> authentications = serviceTicket.getGrantingTicket().getChainedAuthentications();
			final String formatString = "Granted %s ticket [%s] for service [%s] for user [%s]";
			final String type;
			final String principalId = authentications.get(authentications.size() - 1).getPrincipal().getId();

			if (authentications.size() == 1) {
				type = "service";
			} else {
				type = "proxy";
			}
			log.info(String.format(formatString, type, serviceTicket.getId(),service.getId(), principalId));
		}
		return serviceTicket.getId();
	}

	@Audit(action = "SERVICE_TICKET", actionResolverName = "GRANT_SERVICE_TICKET_RESOLVER", resourceResolverName = "GRANT_SERVICE_TICKET_RESOURCE_RESOLVER")
	@Profiled(tag = "GRANT_SERVICE_TICKET", logFailuresSeparately = false)
	@Transactional(readOnly = false)
	public String grantServiceTicket(final String ticketGrantingTicketId, final Service service) throws TicketException {
		return this.grantServiceTicket(ticketGrantingTicketId, service, null);
	}

	/**
	 * @throws IllegalArgumentException
	 *             if the ServiceTicketId or the Credentials are null.
	 */
	@Audit(action = "PROXY_GRANTING_TICKET", actionResolverName = "GRANT_PROXY_GRANTING_TICKET_RESOLVER", resourceResolverName = "GRANT_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER")
	@Profiled(tag = "GRANT_PROXY_GRANTING_TICKET", logFailuresSeparately = false)
	@Transactional(readOnly = false)
	public String delegateTicketGrantingTicket(final String serviceTicketId, final Credentials credentials) throws TicketException {
		Assert.notNull(serviceTicketId, "serviceTicketId cannot be null");
		Assert.notNull(credentials, "credentials cannot be null");

		try {
			final Authentication authentication = this.authenticationManager.authenticate(credentials);

			final ServiceTicket serviceTicket;
			serviceTicket = (ServiceTicket) this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class);

			if (serviceTicket == null || serviceTicket.isExpired()) {
				throw new InvalidTicketException();
			}

			final RegisteredService registeredService = this.servicesManager.findServiceBy(serviceTicket.getService());

			if (registeredService == null || !registeredService.isEnabled() || !registeredService.isAllowedToProxy()) {
				log.warn("ServiceManagement: Service Attempted to Proxy, but is not allowed.  Service: [" + serviceTicket.getService().getId() + "]");
				throw new UnauthorizedProxyingException();
			}

			final TicketGrantingTicket ticketGrantingTicket = serviceTicket.grantTicketGrantingTicket(this.ticketGrantingTicketUniqueTicketIdGenerator.getNewTicketId(TicketGrantingTicket.PREFIX)
									+ "-"+ ((UsernamePasswordCredentials) credentials).getIp(), authentication, this.ticketGrantingTicketExpirationPolicy);
			this.ticketRegistry.addTicket(ticketGrantingTicket);
			
			return ticketGrantingTicket.getId();
		} catch (final AuthenticationException e) {
			throw new TicketCreationException(e);
		}
	}

	/**
	 * @throws IllegalArgumentException
	 *             if the ServiceTicketId or the Service are null.
	 */
	@Audit(action = "SERVICE_TICKET_VALIDATE", actionResolverName = "VALIDATE_SERVICE_TICKET_RESOLVER", resourceResolverName = "VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER")
	@Profiled(tag = "VALIDATE_SERVICE_TICKET", logFailuresSeparately = false)
	@Transactional(readOnly = false)
	public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws TicketException {
		Assert.notNull(serviceTicketId, "serviceTicketId cannot be null");
		Assert.notNull(service, "service cannot be null");

		final ServiceTicket serviceTicket = (ServiceTicket) this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class);

		final RegisteredService registeredService = this.servicesManager.findServiceBy(service);

		if (registeredService == null || !registeredService.isEnabled()) {
			log.warn("ServiceManagement: Service does not exist is not enabled, and thus not allowed to validate tickets.   Service: [" + service.getId() + "]");
			throw new UnauthorizedServiceException("Service not allowed to validate tickets.");
		}

		if (serviceTicket == null) {
			log.info("ServiceTicket [" + serviceTicketId + "] does not exist.");
			throw new InvalidTicketException();
		}

		try {
			synchronized (serviceTicket) {
				if (serviceTicket.isExpired()) {
					log.info("ServiceTicket [" + serviceTicketId + "] has expired.");
					throw new InvalidTicketException();
				}

				if (!serviceTicket.isValidFor(service)) {
					log.error("ServiceTicket [" + serviceTicketId + "] with service ["+ serviceTicket.getService().getId()+ " does not match supplied service [" + service + "]");
					throw new TicketValidationException(serviceTicket.getService());
				}
			}

			final List<Authentication> chainedAuthenticationsList = serviceTicket.getGrantingTicket().getChainedAuthentications();
			final Authentication authentication = chainedAuthenticationsList.get(chainedAuthenticationsList.size() - 1);
			final Principal principal = authentication.getPrincipal();

			final String principalId = determinePrincipalIdForRegisteredService(principal, registeredService, serviceTicket);
			final Authentication authToUse;

			if (!registeredService.isIgnoreAttributes()) {
				final Map<String, Object> attributes = new HashMap<String, Object>();

				for (final String attribute : registeredService.getAllowedAttributes()) {
					final Object value = principal.getAttributes().get(attribute);
					if (value != null) {
						attributes.put(attribute, value);
					}
				}

				final Principal modifiedPrincipal = new SimplePrincipal(principalId, attributes);
				final MutableAuthentication mutableAuthentication = new MutableAuthentication(modifiedPrincipal,authentication.getAuthenticatedDate());
				mutableAuthentication.getAttributes().putAll(authentication.getAttributes());
				mutableAuthentication.getAuthenticatedDate().setTime(authentication.getAuthenticatedDate().getTime());
				authToUse = mutableAuthentication;
			} else {
				final Principal modifiedPrincipal = new SimplePrincipal(principalId, principal.getAttributes());
				authToUse = new MutableAuthentication(modifiedPrincipal, authentication.getAuthenticatedDate());
			}

			final List<Authentication> authentications = new ArrayList<Authentication>();
			for (int i = 0; i < chainedAuthenticationsList.size() - 1; i++) {
				authentications.add(serviceTicket.getGrantingTicket().getChainedAuthentications().get(i));
			}
			authentications.add(authToUse);

			return new ImmutableAssertionImpl(authentications, serviceTicket.getService(), serviceTicket.isFromNewLogin());
		} finally {
			if (serviceTicket.isExpired()) {
				this.serviceTicketRegistry.deleteTicket(serviceTicketId);
			}
		}
	}

	/**
	 * Determines the principal id to use for a {@link RegisteredService} using
	 * the following rules:
	 * 
	 * <ul>
	 * <li>If the service is marked to allow anonymous access, a persistent id
	 * is returned.</li>
	 * <li>If the attribute name matches
	 * {@link RegisteredService#DEFAULT_USERNAME_ATTRIBUTE}, then the default
	 * principal id is returned.</li>
	 * <li>If the service is set to ignore attributes, or the username attribute
	 * exists in the allowed attributes for the service, the corresponding
	 * attribute value will be returned.</li>
	 * <li>Otherwise, the default principal's id is returned as the username
	 * attribute with an additional warning.</li>
	 * </ul>
	 * 
	 * @param principal
	 *            The principal object to be validated and constructed
	 * @param registeredService
	 *            Requesting service for which a principal is being validated.
	 * @param serviceTicket
	 *            An instance of the service ticket used for validation
	 * 
	 * @return The principal id to use for the requesting registered service
	 */
	private String determinePrincipalIdForRegisteredService(final Principal principal, final RegisteredService registeredService, final ServiceTicket serviceTicket) {
		String principalId = null;
		final String serviceUsernameAttribute = registeredService.getUsernameAttribute();

		if (registeredService.isAnonymousAccess()) {
			principalId = this.persistentIdGenerator.generate(principal,serviceTicket.getService());
		} else if (StringUtils.isBlank(serviceUsernameAttribute)) {
			principalId = principal.getId();
		} else {
			if ((registeredService.isIgnoreAttributes() || registeredService.getAllowedAttributes().contains(serviceUsernameAttribute)) && principal.getAttributes().containsKey(serviceUsernameAttribute)) {
				principalId = principal.getAttributes().get(registeredService.getUsernameAttribute()).toString();
			} else {
				principalId = principal.getId();
				final Object[] errorLogParameters = new Object[] {principalId, registeredService.getUsernameAttribute(),principal.getAttributes(), registeredService.getServiceId(), principalId };
				log.warn("Principal [{}] did not have attribute [{}] among attributes [{}] so CAS cannot " + "provide on the validation response the user attribute the registered service [{}] expects. "
								+ "CAS will instead return the default username attribute [{}]", errorLogParameters);
			}
		}

		log.debug("Principal id to return for service [{}] is [{}]. The default principal id is [{}].", new Object[] { registeredService.getName(), principal.getId(), principalId });
		return principalId;
	}

	/**
	 * @throws IllegalArgumentException
	 *             if the credentials are null.
	 */
	@Audit(action = "TICKET_GRANTING_TICKET", actionResolverName = "CREATE_TICKET_GRANTING_TICKET_RESOLVER", resourceResolverName = "CREATE_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER")
	@Profiled(tag = "CREATE_TICKET_GRANTING_TICKET", logFailuresSeparately = false)
	@Transactional(readOnly = false)
	public String createTicketGrantingTicket(final Credentials credentials) throws TicketException {
		Assert.notNull(credentials, "credentials cannot be null");
		UsernamePasswordCredentials up = ((UsernamePasswordCredentials) credentials);
		try {
			int count = 0;
			long lastTimeUsed = Long.MAX_VALUE;
			Ticket lastTicket = null;
			Collection<Ticket> list = this.ticketRegistry.getTickets();
			// 查找最早生成的ticket,即创建时间最小的ticket gjwu@isoftstone.com 2014-02-17
			for (Ticket t : list) {
				if (TicketGrantingTicket.class.isAssignableFrom(t.getClass())) {
					TicketGrantingTicketImpl tgt = (TicketGrantingTicketImpl) t;
					// 同一IP登录产生多个票据问题。
					if (tgt.getAuthentication().getPrincipal().getId().equals(up.getUsername()) && tgt.getId().endsWith(up.getIp() + "")) {
						this.ticketRegistry.deleteTicket(tgt.getId());
					} else if (tgt.getAuthentication().getPrincipal().getId().equals(up.getUsername()) && !tgt.getId().endsWith(up.getIp() + "")) {
						// 同一台机器多个浏览器登录不限制
						count++;
						if (((TicketGrantingTicketImpl) t).getCreationTime() < lastTimeUsed) {
							lastTicket = t;
						}
					}
				}
			}

			// 是否超过了最大登录数限制 gjwu@isoftstone.com 2014-02-17
			if (this.maximumSessions != -1 && count >= this.maximumSessions) {
				// 是否覆盖登录，即把最早登录的ticket过期，再生成一个新的ticket
				if ("true".equalsIgnoreCase(up.getOverideLogin()) && lastTicket != null) {
					((TicketGrantingTicketImpl) lastTicket).expire();
				} else {
					// 提示超过最大登录数限制，不允许再登录
					throw new TicketCaptchaException(TicketCaptchaException.ACCOUN_MULTI);
				}
			}

			final Authentication authentication = this.authenticationManager.authenticate(credentials);

			final TicketGrantingTicket ticketGrantingTicket = new TicketGrantingTicketImpl(
					this.ticketGrantingTicketUniqueTicketIdGenerator.getNewTicketId(TicketGrantingTicket.PREFIX) + "-" + ((UsernamePasswordCredentials) credentials).getIp(),
					authentication, this.ticketGrantingTicketExpirationPolicy);
			this.ticketRegistry.addTicket(ticketGrantingTicket);
			return ticketGrantingTicket.getId();
		} catch (final AuthenticationException e) {
			throw new TicketCreationException(e);
		}
	}

	/**
	 * Method to set the TicketRegistry.
	 *
	 * @param ticketRegistry
	 *            the TicketRegistry to set.
	 */
	public void setTicketRegistry(final TicketRegistry ticketRegistry) {
		this.ticketRegistry = ticketRegistry;
		if (this.serviceTicketRegistry == null) {
			this.serviceTicketRegistry = ticketRegistry;
		}
	}

	public void setServiceTicketRegistry(final TicketRegistry serviceTicketRegistry) {
		this.serviceTicketRegistry = serviceTicketRegistry;
	}

	/**
	 * Method to inject the AuthenticationManager into the class.
	 *
	 * @param authenticationManager
	 *            The authenticationManager to set.
	 */
	public void setAuthenticationManager(final AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}

	/**
	 * Method to inject the TicketGrantingTicket Expiration Policy.
	 *
	 * @param ticketGrantingTicketExpirationPolicy
	 *            The ticketGrantingTicketExpirationPolicy to set.
	 */
	public void setTicketGrantingTicketExpirationPolicy(final ExpirationPolicy ticketGrantingTicketExpirationPolicy) {
		this.ticketGrantingTicketExpirationPolicy = ticketGrantingTicketExpirationPolicy;
	}

	/**
	 * Method to inject the Unique Ticket Id Generator into the class.
	 *
	 * @param uniqueTicketIdGenerator
	 *            The uniqueTicketIdGenerator to use
	 */
	public void setTicketGrantingTicketUniqueTicketIdGenerator(final UniqueTicketIdGenerator uniqueTicketIdGenerator) {
		this.ticketGrantingTicketUniqueTicketIdGenerator = uniqueTicketIdGenerator;
	}

	/**
	 * Method to inject the TicketGrantingTicket Expiration Policy.
	 *
	 * @param serviceTicketExpirationPolicy
	 *            The serviceTicketExpirationPolicy to set.
	 */
	public void setServiceTicketExpirationPolicy(final ExpirationPolicy serviceTicketExpirationPolicy) {
		this.serviceTicketExpirationPolicy = serviceTicketExpirationPolicy;
	}

	public void setUniqueTicketIdGeneratorsForService(final Map<String, UniqueTicketIdGenerator> uniqueTicketIdGeneratorsForService) {
		this.uniqueTicketIdGeneratorsForService = uniqueTicketIdGeneratorsForService;
	}

	public void setServicesManager(final ServicesManager servicesManager) {
		this.servicesManager = servicesManager;
	}

	public void setPersistentIdGenerator(final PersistentIdGenerator persistentIdGenerator) {
		this.persistentIdGenerator = persistentIdGenerator;
	}

	/**
	 *
	 * 获取同账号最大登录数 <br>
	 * 
	 * <pre>
	 * 方法getMaximumSessions的详细说明 <br>
	 * 编写者：吴国建
	 * 创建时间：2014-2-17 上午11:49:28
	 * </pre>
	 * 
	 * @param 参数名
	 *            说明
	 * @return int 说明
	 * @throws 异常类型
	 *             说明
	 */
	public int getMaximumSessions() {
		return maximumSessions;
	}

	public void setMaximumSessions(int maximumSessions) {
		this.maximumSessions = maximumSessions;
	}
}
